/*
* This file is part of washmatique-lib.

* washmatique-lib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.

* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

* Copyright (C) 2011 boris Sabadach
* http://washmatique.fr/

*/


package fr.washmatique.spark.components.navigators {

	import flash.events.Event;

	import fr.washmatique.spark.components.ISelectableVisualElement;

	import mx.core.IVisualElement;
	import mx.core.UIComponent;
	import mx.events.FlexEvent;

	import spark.components.Group;
	import spark.effects.easing.IEaser;
	import spark.events.IndexChangeEvent;
	import spark.layouts.HorizontalAlign;
	import spark.layouts.VerticalAlign;
	import spark.layouts.VerticalLayout;
	import spark.layouts.supportClasses.LayoutBase;

	/**
	 *  Dispatched after the accordion element header selection has changed.
	 *  This event is dispatched when the user interacts with the control.
	 *
	 *  When you change the value of the selectedIndex properties programmatically,
	 *  the control does not dispatch the change event.
	 *  It dispatches the valueCommit event instead.
	 *
	 *  @eventType spark.events.IndexChangeEvent.CHANGE
	 *
	 */
	[Event(name="change", type="spark.events.IndexChangeEvent")]

	/**
	 *  Skin class that will be applied on each element header.
	 */
	[Style(name="headerSkinClass", type="Class")]

	public class Accordion extends Group {


		private var _openDuration:uint=250;

		private var _openDurationChanged:Boolean=false;

		private var _easer:IEaser;

		private var _easerChanged:Boolean=false;


		private var _foldable:Boolean=false;

		private var _foldableChanged:Boolean=false;

		private var _selectedIndex:int=-1;

		private var _newSelectedIndex:int=-1;

		private var _newSelectedIndexPending:Boolean=false;

		private var _dispatchChangeAfterSelection:Boolean=false;

		private var _mustDispatchValueCommitEvent:Boolean=false;


		public function Accordion() {
			super();
			super.layout=createDefaultLayout();
		}





		public function get foldable():Boolean {
			return _foldable;
		}

		/**
		 *  a flag that tells if the accordion can be fully folded ( no item selected).
		 *  The default value is 250
		 *  @param value
		 *
		 *  @default 250
		 */
		[Bindable("valueCommit")]
		[Inspectable(category="General", defaultValue="false")]
		public function set foldable(value:Boolean):void {
			if (_foldable == value)
				return;
			_foldable=value;
			_foldableChanged=true;
			invalidateProperties();
		}


		/**
		 *  The open duration in milliseconds for transition animation when selection change.
		 *  The default value is 250
		 *  @param value
		 *
		 *  @default 250
		 */
		[Bindable("valueCommit")]
		[Inspectable(category="General", defaultValue="250")]
		public function get openDuration():uint {
			return _openDuration;
		}


		public function set openDuration(value:uint):void {
			if (_openDuration == value) {
				return;
			}
			_openDuration=value;
			_openDurationChanged=true;
			invalidateProperties();
		}


		/**
		 * easer type for transition animation when selection change
		 */
		[Bindable("valueCommit")]
		public function get easer():IEaser {
			return _easer;
		}


		public function set easer(value:IEaser):void {
			if (_easer !== value) {
				_easer=value;
				_easerChanged=true
			}
		}

		public function set selectedIndex(i:int):void {
			if (i == _selectedIndex)
				return;
			_newSelectedIndex=i;
			_dispatchChangeAfterSelection=false;
			_newSelectedIndexPending=true;
			invalidateProperties();
		}

		[Bindable("change")]
		[Bindable("valueCommit")]
		[Inspectable(category="General", defaultValue="0")]
		/**
		 *  The 0-based index of the selected item, or -1 if no item is selected.
		 *  Setting the selectedIndex property deselects the currently selected
		 *  item.
		 *  The value is always between -1 and numchildren-1
		 *
		 *  When the user changes the selectedIndex property by interacting with the control,
		 *  the control dispatches the change event.
		 *  When you change the value of the selectedIndex property programmatically,
		 *  it dispatches the valueCommit event.
		 *
		 *  @default 0
		 *
		 */
		public function get selectedIndex():int {
			return _selectedIndex;
		}






		//--------------------------------------------------------------------------
		//
		//  Overridden methods
		//
		//--------------------------------------------------------------------------

		override public function initialize():void {
			super.initialize();
			registerListeners();
		}


		/**
		 *  @private
		 */
		override protected function commitProperties():void {
			super.commitProperties();
			;
			if (_newSelectedIndexPending) {
				commitSelectedIndex();
				_newSelectedIndexPending=false;
			}
			configureElements();
			dispatchValueCommitEventIfNecessary();

		}



		/**
		 *  @private
		 *  after the measurement pass each element is resized to fit the accordion width
		 */
		override protected function measure():void {
			super.measure();
			for (var i:int=0; i < numElements; i++) {
				var element:AccordionElement=getElementAt(i) as AccordionElement;
				element.width=width;
			}
		}


		/**
		 * @private
		 * this method is overriden to replace each direct element by an AccordionElement instance wrapping the element
		 */
		override public function set mxmlContent(value:Array):void {
			if (value) {
				var mxmlContent:Array=new Array();
				var lenght:uint=value.length;
				for (var i:int=0; i < lenght; i++) {
					mxmlContent.push(createAccordionElementIfNeededWith(value[i]));
				}
				super.mxmlContent=mxmlContent;
				return;
			}
			super.mxmlContent=value;
		}


		/**
		 *  @private
		 */
		override public function stylesInitialized():void {
			super.stylesInitialized();
			var headerSkinClass:Class=getStyle("headerSkinClass");
			if (!headerSkinClass)
				return;
			for (var i:int=0; i < numElements; i++) {
				var element:AccordionElement=getElementAt(i) as AccordionElement;
				element.setHeaderSkinClass(headerSkinClass);
			}
		}


		/**
		 *  @private
		 *  replace added element by an AccordionElement instance wrapping the element if needed
		 */
		override public function addElement(element:IVisualElement):IVisualElement {
			var accordionElement:AccordionElement=createAccordionElementIfNeededWith(element as UIComponent);
			return super.addElement(accordionElement);
		}

		/**
		 * @private
		 * replace added element by an AccordionElement instance wrapping the element if needed
		 */
		override public function addElementAt(element:IVisualElement, index:int):IVisualElement {
			var accordionElement:AccordionElement=createAccordionElementIfNeededWith(element as UIComponent);
			return super.addElementAt(accordionElement, index);
		}




		//--------------------------------------------------------------------------
		//
		//  protected and private methods
		//
		//--------------------------------------------------------------------------



		private function registerListeners():void {
			addEventListener(Event.CHANGE, elementHeaderSelectionChangeHandler, true, 0, false);
		}

		/**
		 * @private
		 * sets some properties (openduration, easer) on each accordion element
		 *
		 */
		protected function configureElements():void {
			for (var i:int=0; i < numElements; i++) {
				var element:AccordionElement=getElementAt(i) as AccordionElement;

				if (!element.openDuration || _openDurationChanged) {
					element.openDuration=_openDuration;
				}
				if (!element.easer || _easerChanged) {
					element.easer=_easer;
				}
				if (_foldableChanged) {
					element.setHeaderSwitchability(foldable);
				}
			}
		}




		/**
		 * @private
		 * create if necessary a new AccordionElement instance with the component passed as a parameter
		 * @param content: a component to be wrapped
		 * @return AccordionElement instance
		 *
		 */
		protected function createAccordionElementIfNeededWith(content:UIComponent):AccordionElement {
			var accordionElement:AccordionElement;
			var headerLabel:String;
			if (content is AccordionElement) {
				accordionElement=content as AccordionElement;
			} else {
				accordionElement=new AccordionElement();
				accordionElement.addElement(content);
				headerLabel=content.name; // set the headerLabel with the name of the component wrapped
			}
			if (!accordionElement.headerLabel) {
				accordionElement.headerLabel=headerLabel;
			}

			return accordionElement;
		}





		/**
		 * @private
		 * Sets the default VerticalLayout.
		 *
		 */
		protected function createDefaultLayout():LayoutBase {
			var defaultLayout:VerticalLayout=new VerticalLayout();
			defaultLayout.gap=0;
			defaultLayout.clipAndEnableScrolling=true;
			defaultLayout.horizontalAlign=HorizontalAlign.LEFT;
			defaultLayout.verticalAlign=VerticalAlign.TOP;
			defaultLayout.variableRowHeight=true;
			autoLayout=true;
			return defaultLayout;

		}





		/**
		 * @private
		 * commits the pending selected index and dispatches an IndexChangeEvent if necessary
		 *
		 */
		private function commitSelectedIndex():void {
			if (_newSelectedIndex == -1 && !foldable) {
				_newSelectedIndex=0;
			}

			var oldIndex:int=_selectedIndex;
			// sets the new index to -1 in case an element is selected again and accordion is foldable
			if (_newSelectedIndex == _selectedIndex) {
				if (_foldable && !_foldableChanged) {
					_newSelectedIndex=-1;
				} else {
					return;
				}
			}
			selectOrDeselectElements();
			_selectedIndex=_newSelectedIndex;
			_mustDispatchValueCommitEvent=true;

			if (_dispatchChangeAfterSelection) {
				dispatchEvent(new IndexChangeEvent(IndexChangeEvent.CHANGE, false, false, oldIndex, _selectedIndex));
				_dispatchChangeAfterSelection=false;
			}

		}

		private function dispatchValueCommitEventIfNecessary():void {
			if (_easerChanged) {
				_easerChanged=false;
				_mustDispatchValueCommitEvent=true;
			}

			if (_openDurationChanged) {
				_openDurationChanged=false;
				_mustDispatchValueCommitEvent=true;
			}
			if (_foldableChanged) {
				_foldableChanged=false;
				_mustDispatchValueCommitEvent=true;
			}

			if (_mustDispatchValueCommitEvent) {
				dispatchEvent(new FlexEvent(FlexEvent.VALUE_COMMIT));
				_mustDispatchValueCommitEvent=false;
			}
		}


		/**
		 *@private
		 *
		 */
		private function selectOrDeselectElements():void {
			var element:AccordionElement;
			if (foldable && !_foldableChanged && _selectedIndex == _newSelectedIndex && _newSelectedIndex != -1) {
				element=getElementAt(_newSelectedIndex) as AccordionElement;
				element.opened=!element.opened;
				return;
			}

			for (var i:int=0; i < numElements; i++) {
				element=getElementAt(i) as AccordionElement;
				element.opened=_newSelectedIndex == i;
			}

		}





		/**
		 * @private
		 * selection change event handler
		 * @param evt
		 *
		 */
		private function elementHeaderSelectionChangeHandler(evt:Event):void {
			if (!(evt.target is ISelectableVisualElement)) {
				return
			}
			var accordionElementHeader:IVisualElement=evt.target as IVisualElement;
			var accordionElement:IVisualElement=accordionElementHeader.owner as IVisualElement
			if (accordionElement == null || accordionElement.owner != this)
				return;
			_newSelectedIndex=getElementIndex(accordionElement);
			_newSelectedIndexPending=true;
			_dispatchChangeAfterSelection=true;
			invalidateProperties();

		}



	}
}

